package GUI;

import java.awt.BasicStroke;
import java.awt.BorderLayout;
import java.awt.Button;
import java.awt.Color;
import java.awt.Container;
import java.awt.Dimension;
import java.awt.FlowLayout;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.GridLayout;
import java.awt.Paint;
import java.awt.Rectangle;
import java.awt.Shape;
import java.awt.Toolkit;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.ItemEvent;
import java.awt.event.ItemListener;
import java.awt.geom.GeneralPath;
import java.lang.reflect.InvocationTargetException;

import javax.swing.AbstractButton;
import javax.swing.BorderFactory;
import javax.swing.Box;
import javax.swing.BoxLayout;
import javax.swing.JButton;
import javax.swing.JCheckBox;
import javax.swing.JComboBox;
import javax.swing.JDialog;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.SwingUtilities;
import javax.swing.ToolTipManager;
import javax.swing.UIManager;
import javax.swing.UnsupportedLookAndFeelException;

import org.apache.commons.collections15.Predicate;
import org.apache.commons.collections15.Transformer;
import org.jvnet.substance.api.skin.SubstanceGeminiLookAndFeel;
import org.jvnet.substance.skin.BusinessBlackSteelSkin;
import org.jvnet.substance.skin.MagmaSkin;
import org.jvnet.substance.skin.SubstanceMagmaLookAndFeel;
import org.jvnet.substance.skin.SubstanceNebulaBrickWallLookAndFeel;
import org.jvnet.substance.skin.SubstanceNebulaLookAndFeel;
import org.jvnet.substance.skin.SubstanceRavenGraphiteLookAndFeel;
import org.jvnet.substance.skin.SubstanceRavenLookAndFeel;
import org.jvnet.substance.skin.SubstanceTwilightLookAndFeel;

import edu.uci.ics.jung.algorithms.layout.FRLayout;
import edu.uci.ics.jung.algorithms.layout.ISOMLayout;
import edu.uci.ics.jung.graph.Graph;
import edu.uci.ics.jung.graph.util.Context;
import edu.uci.ics.jung.graph.util.EdgeType;
import edu.uci.ics.jung.visualization.DefaultVisualizationModel;
import edu.uci.ics.jung.visualization.GraphZoomScrollPane;
import edu.uci.ics.jung.visualization.Layer;
import edu.uci.ics.jung.visualization.VisualizationModel;
import edu.uci.ics.jung.visualization.VisualizationViewer;
import edu.uci.ics.jung.visualization.VisualizationServer.Paintable;
import edu.uci.ics.jung.visualization.control.CrossoverScalingControl;
import edu.uci.ics.jung.visualization.control.DefaultModalGraphMouse;
import edu.uci.ics.jung.visualization.control.SatelliteVisualizationViewer;
import edu.uci.ics.jung.visualization.control.ScalingControl;
import edu.uci.ics.jung.visualization.decorators.AbstractVertexShapeTransformer;
import edu.uci.ics.jung.visualization.decorators.PickableEdgePaintTransformer;
import edu.uci.ics.jung.visualization.decorators.PickableVertexPaintTransformer;
import edu.uci.ics.jung.visualization.decorators.ToStringLabeller;
import edu.uci.ics.jung.visualization.renderers.GradientVertexRenderer;
import edu.uci.ics.jung.visualization.renderers.Renderer;
import edu.uci.ics.jung.visualization.transform.shape.ShapeTransformer;

import Config.Configure;
import Statistics.IDGraphToJungGraph;
import Statistics.IDGraphToJungGraph.MyEdge;

/**
 * @author Xenophon
 * @version 2010-1-14
 */
public class MainAppGUI extends JFrame implements ActionListener {
	private static final String instructions = "<html>"
			+ "<b><h2><center>Instructions for Mouse Listeners</center></h2></b>"
			+ "<p>There are two modes, Transforming and Picking."
			+ "<p>The modes are selected with a combo box."
			+

			"<p><p><b>Transforming Mode:</b>"
			+ "<ul>"
			+ "<li>Mouse1+drag pans the graph"
			+ "<li>Mouse1+Shift+drag rotates the graph"
			+ "<li>Mouse1+CTRL(or Command)+drag shears the graph"
			+ "</ul>"
			+

			"<b>Picking Mode:</b>"
			+ "<ul>"
			+ "<li>Mouse1 on a Vertex selects the vertex"
			+ "<li>Mouse1 elsewhere unselects all Vertices"
			+ "<li>Mouse1+Shift on a Vertex adds/removes Vertex selection"
			+ "<li>Mouse1+drag on a Vertex moves all selected Vertices"
			+ "<li>Mouse1+drag elsewhere selects Vertices in a region"
			+ "<li>Mouse1+Shift+drag adds selection of Vertices in a new region"
			+ "<li>Mouse1+CTRL on a Vertex selects the vertex and centers the display on it"
			+ "</ul>"
			+ "<b>Both Modes:</b>"
			+ "<ul>"
			+ "<li>Mousewheel scales with a crossover value of 1.0.<p>"
			+ "     - scales the graph layout when the combined scale is greater than 1<p>"
			+ "     - scales the graph view when the combined scale is less than 1";

	private static final String configure_file_path = "./configure";

	private DataFrontier dataFrontier;
	private Graph<String, MyEdge> graph;

	// Bottom Controllers
	private JDialog helpDialog;
	private JPanel controls;
	// Frame
	private Container content;
	private Container panel;
	private JPanel rightPanel;
	GraphZoomScrollPane gzsp;
	// Right area controllers
	private JPanel nePanel;
	private Box neBox;
	private JCheckBox degreeFilter;
	private JCheckBox showID;
	private JCheckBox sizeByInDegree;
	private JCheckBox showArrow;
	private Box seBox;
	private Paintable viewGrid;
	private Box gPBox;
	private JCheckBox degreeDisButton;
	private DegreeDialog degreeDialog;
	private JCheckBox sccView;
	private SCCViewDialog  sccViewDialog;

	
	
	private VisualizationViewer<String, MyEdge> vv1;
	private VisualizationViewer<String, MyEdge> vv2;
	private VertexShapeSizeAspect<String,MyEdge>   vertexSizeAspect;
	private VertexDisplayPredicate<String, MyEdge> smallDegreeFilter;
	private DirectionDisplayPredicate<String,MyEdge> showArrowPredicate;

	public MainAppGUI() {
		// First, we get configure data and stored graph data
		Configure.SetConfigureFile(configure_file_path);
		try {
			Configure.GetConfigure();
		} catch (Exception e) {
			e.printStackTrace();
		}
		dataFrontier = new DataFrontier();
		graph = dataFrontier.GetJungFormatGraph();

		smallDegreeFilter = new VertexDisplayPredicate<String, MyEdge>(false);
		vertexSizeAspect  = new VertexShapeSizeAspect<String, MyEdge>(graph);
		showArrowPredicate = new DirectionDisplayPredicate<String, MyEdge>(false);
		
		creatGraphView();
		addControllerInTheRightArea();
		createMainFrame();
		addBottomControllers();
	}

	protected void addBottomControllers() {
		helpDialog = new JDialog();
		helpDialog.getContentPane().add(new JLabel(instructions));

		// create a GraphMouse for the main view
		// 
		final DefaultModalGraphMouse graphMouse = new DefaultModalGraphMouse();
		vv1.setGraphMouse(graphMouse);

		final ScalingControl scaler = new CrossoverScalingControl();

		JButton plus = new JButton("+");
		plus.addActionListener(new ActionListener() {
			public void actionPerformed(ActionEvent e) {
				scaler.scale(vv1, 1.1f, vv1.getCenter());
			}
		});
		JButton minus = new JButton("-");
		minus.addActionListener(new ActionListener() {
			public void actionPerformed(ActionEvent e) {
				scaler.scale(vv1, 1 / 1.1f, vv1.getCenter());
			}
		});

		JComboBox modeBox = graphMouse.getModeComboBox();
		modeBox.addItemListener(((DefaultModalGraphMouse) vv2.getGraphMouse())
				.getModeListener());

		JCheckBox gridBox = new JCheckBox("Show Grid");
		gridBox.addItemListener(new ItemListener() {
			public void itemStateChanged(ItemEvent e) {
				showGrid(vv2, e.getStateChange() == ItemEvent.SELECTED);
			}
		});
		JButton help = new JButton("Help");
		help.addActionListener(new ActionListener() {
			public void actionPerformed(ActionEvent e) {
				helpDialog.pack();
				helpDialog.setVisible(true);
			}
		});

		controls = new JPanel();
		controls.add(plus);
		controls.add(minus);
		controls.add(modeBox);
		controls.add(gridBox);
		controls.add(help);
		content.add(panel);
		content.add(controls, BorderLayout.SOUTH);
	}

	protected void createMainFrame() {
		content = getContentPane();
		panel = new JPanel(new BorderLayout());
		gzsp = new GraphZoomScrollPane(vv1);
		gzsp.setBorder(BorderFactory.createTitledBorder("ID Graph View"));
		panel.add(gzsp);
		panel.add(rightPanel, BorderLayout.EAST);
	}

	protected void creatGraphView() {
		// Second, we create the main graph view
		// the preferred sizes for the two views
		Toolkit toolkit = Toolkit.getDefaultToolkit();
		Dimension preferredSize1 = new Dimension(
				toolkit.getScreenSize().width - 245,
				toolkit.getScreenSize().height - 145);
		Dimension preferredSize2 = new Dimension(200, 200);

		// create one layout for the graph
		FRLayout<String, MyEdge> layout = new FRLayout<String, MyEdge>(graph);
		layout.setMaxIterations(500);
		// ISOMLayout<String, MyEdge> layout = new ISOMLayout<String,
		// MyEdge>(graph);

		// create one model that both views will share
		VisualizationModel<String, MyEdge> vm = new DefaultVisualizationModel<String, MyEdge>(
				layout, preferredSize1);

		// create 2 views that share the same model
		vv1 = new VisualizationViewer<String, MyEdge>(vm, preferredSize1);
		vv2 = new SatelliteVisualizationViewer<String, MyEdge>(vv1,
				preferredSize2);
		vv1.setBackground(Color.black);
		vv1.getRenderContext().setEdgeDrawPaintTransformer(
				new PickableEdgePaintTransformer<MyEdge>(vv1
						.getPickedEdgeState(), Color.orange, Color.cyan));
		vv1.getRenderContext().setVertexFillPaintTransformer(
				new PickableVertexPaintTransformer<String>(vv1
						.getPickedVertexState(), Color.white, Color.yellow));
		vv1.getRenderContext().setVertexDrawPaintTransformer(new Transformer<String, Paint>() {
			public Paint transform(String v) {
				return Color.cyan;
			}
		});

		vv1.getRenderContext().setVertexShapeTransformer(vertexSizeAspect);
		vv1.getRenderContext().setVertexIncludePredicate(smallDegreeFilter);
		vv1.getRenderContext().setEdgeArrowPredicate(showArrowPredicate);
		

		vv2.getRenderContext().setEdgeDrawPaintTransformer(
				new PickableEdgePaintTransformer<MyEdge>(vv2
						.getPickedEdgeState(), Color.yellow, Color.cyan));
		vv2.getRenderContext().setVertexFillPaintTransformer(
				new PickableVertexPaintTransformer<String>(vv2
						.getPickedVertexState(), Color.red, Color.yellow));
		vv2.getRenderContext().setVertexShapeTransformer(vertexSizeAspect);
		vv2.getRenderContext().setVertexIncludePredicate(smallDegreeFilter);
		vv2.getRenderContext().setEdgeArrowPredicate(showArrowPredicate);
		
		//vv1.getRenderer().setVertexRenderer(
		//		new GradientVertexRenderer<String, MyEdge>(Color.red,
		//				Color.white, true));
		// vv1.getRenderContext()
		// .setVertexLabelTransformer(new ToStringLabeller());
		// vv1.getRenderer().getVertexLabelRenderer().setPosition(
		// Renderer.VertexLabel.Position.SE);

		ScalingControl vv2Scaler = new CrossoverScalingControl();
		vv2.scaleToLayout(vv2Scaler);

		viewGrid = new ViewGrid(vv2, vv1);

		// add default listener for ToolTips
		vv1.setVertexToolTipTransformer(new ToStringLabeller());
		vv2.setVertexToolTipTransformer(new ToStringLabeller());

		// vv2.getRenderContext().setVertexLabelTransformer(
		// vv1.getRenderContext().getVertexLabelTransformer());

		ToolTipManager.sharedInstance().setDismissDelay(10000);

	}

	protected void addControllerInTheRightArea() {
		rightPanel = new JPanel(new GridLayout(2, 1));
		rightPanel.setBorder(BorderFactory.createEmptyBorder(2, 2, 2, 2));

		addControllerInTheNEArea();
		seBox = Box.createVerticalBox();
		seBox.setBorder(BorderFactory.createTitledBorder("Global View"));
		seBox.add(vv2);
		rightPanel.add(seBox);
	}

	protected void addControllerInTheNEArea() {
		nePanel = new JPanel();
		nePanel.setLayout(new BorderLayout());
		neBox = Box.createVerticalBox();
		neBox.setBorder(BorderFactory.createTitledBorder("Graph View Setting"));
		degreeFilter = new JCheckBox("Degree < "
				+ VertexDisplayPredicate.MIN_DEGREE + " Filter", false);
		degreeFilter.addActionListener(this);
		showID = new JCheckBox("Show ID");
		showID.addActionListener(this);
		sizeByInDegree = new JCheckBox("Vertex size by its indegree");
		sizeByInDegree.addActionListener(this);
		showArrow = new JCheckBox("Show arrow");
		showArrow.addActionListener(this);
		neBox.add(showID);
		neBox.add(degreeFilter);
		neBox.add(sizeByInDegree);
		neBox.add(showArrow);
	    gPBox = Box.createVerticalBox();
	    gPBox.setBorder(BorderFactory.createTitledBorder("Global Property"));
	  
        
  
	    degreeDisButton = new JCheckBox("Degree Distribution");
 
	    
		degreeDialog = new DegreeDialog("Degree Distribution", dataFrontier.GetGlobalProperty());
	    degreeDisButton.addActionListener(this);
	    sccView = new JCheckBox("Local View");
	    sccViewDialog = new SCCViewDialog("Local View",IDGraphToJungGraph.ToSparseGraph(dataFrontier.GenerateRandomSubGraph(100, 20)),dataFrontier.GetSeed());
	    sccView.addActionListener(this);
	  
		
	    gPBox.add(degreeDisButton);
	    degreeDisButton.setBorder(BorderFactory.createEmptyBorder(5, 5, 5, 5));
	    gPBox.add(sccView);
	    sccView.setBorder(BorderFactory.createEmptyBorder(5, 5, 5, 5));



	    
	    nePanel.add(neBox,BorderLayout.NORTH);
	    nePanel.add(gPBox,BorderLayout.CENTER);
		rightPanel.add(nePanel);	
	
	}

	protected void showGrid(VisualizationViewer vv, boolean state) {
		if (state == true) {
			vv.addPreRenderPaintable(viewGrid);
		} else {
			vv.removePreRenderPaintable(viewGrid);
		}
		vv.repaint();
	}

	/**
	 * draws a grid on the SatelliteViewer's lens
	 * 
	 * @author Tom Nelson
	 * 
	 */
	static class ViewGrid implements Paintable {

		VisualizationViewer master;
		VisualizationViewer vv;

		public ViewGrid(VisualizationViewer vv, VisualizationViewer master) {
			this.vv = vv;
			this.master = master;
		}

		public void paint(Graphics g) {
			ShapeTransformer masterViewTransformer = master.getRenderContext()
					.getMultiLayerTransformer().getTransformer(Layer.VIEW);
			ShapeTransformer masterLayoutTransformer = master
					.getRenderContext().getMultiLayerTransformer()
					.getTransformer(Layer.LAYOUT);
			ShapeTransformer vvLayoutTransformer = vv.getRenderContext()
					.getMultiLayerTransformer().getTransformer(Layer.LAYOUT);

			Rectangle rect = master.getBounds();
			GeneralPath path = new GeneralPath();
			path.moveTo(rect.x, rect.y);
			path.lineTo(rect.width, rect.y);
			path.lineTo(rect.width, rect.height);
			path.lineTo(rect.x, rect.height);
			path.lineTo(rect.x, rect.y);

			for (int i = 0; i <= rect.width; i += rect.width / 10) {
				path.moveTo(rect.x + i, rect.y);
				path.lineTo(rect.x + i, rect.height);
			}
			for (int i = 0; i <= rect.height; i += rect.height / 10) {
				path.moveTo(rect.x, rect.y + i);
				path.lineTo(rect.width, rect.y + i);
			}
			Shape lens = path;
			lens = masterViewTransformer.inverseTransform(lens);
			lens = masterLayoutTransformer.inverseTransform(lens);
			lens = vvLayoutTransformer.transform(lens);
			Graphics2D g2d = (Graphics2D) g;
			Color old = g.getColor();
			g.setColor(Color.cyan);
			g2d.draw(lens);

			path = new GeneralPath();
			path.moveTo((float) rect.getMinX(), (float) rect.getCenterY());
			path.lineTo((float) rect.getMaxX(), (float) rect.getCenterY());
			path.moveTo((float) rect.getCenterX(), (float) rect.getMinY());
			path.lineTo((float) rect.getCenterX(), (float) rect.getMaxY());
			Shape crosshairShape = path;
			crosshairShape = masterViewTransformer
					.inverseTransform(crosshairShape);
			crosshairShape = masterLayoutTransformer
					.inverseTransform(crosshairShape);
			crosshairShape = vvLayoutTransformer.transform(crosshairShape);
			g.setColor(Color.black);
			g2d.setStroke(new BasicStroke(3));
			g2d.draw(crosshairShape);

			g.setColor(old);
		}

		public boolean useTransform() {
			return true;
		}
	}

	/**
	 * A lot of Transformers begin here...
	 */
	private static class VertexShapeSizeAspect<V, E> extends
			AbstractVertexShapeTransformer<V> implements Transformer<V, Shape> {
		private static int minVertexSize = 2;
		private static boolean scale = false;
		
		public VertexShapeSizeAspect(final Graph<String, MyEdge> graph) {
			setSizeTransformer(new Transformer<V, Integer>() {
				public Integer transform(V v) {
					if(scale)
					{
						double para = Math.sqrt(graph.getInEdges((String) v).size()+1);
						return  (int)(minVertexSize < 20 * 20 / graph.getVertexCount() ? para*20 * 20 / graph.getVertexCount()
								: para*minVertexSize);
					}else
						return minVertexSize < 20 * 20 / graph.getVertexCount()?20 * 20 / graph.getVertexCount()
								: minVertexSize;
				}
			});
		}

		public void setScale(boolean scale)
		{
			this.scale = scale;
		}
		
		public Shape transform(V v) {
			return factory.getEllipse(v);
		}
	}

	private final static class VertexDisplayPredicate<V, E> implements
			Predicate<Context<Graph<V, E>, V>> {
		protected boolean filter_small;
		protected final static int MIN_DEGREE = 2;

		public VertexDisplayPredicate(boolean filter) {
			this.filter_small = filter;
		}

		public void filterSmall(boolean b) {
			filter_small = b;
		}

		public boolean evaluate(Context<Graph<V, E>, V> context) {
			Graph<V, E> graph = context.graph;
			V v = context.element;
			if (filter_small)
				return (graph.inDegree(v) > MIN_DEGREE||graph.outDegree(v)>MIN_DEGREE);
			else
				return true;
		}
	}
	
	//Show arrow or not
	private final static class DirectionDisplayPredicate<V, E> implements
			Predicate<Context<Graph<V, E>, E>>
	{
		protected boolean show_d;
		public DirectionDisplayPredicate(boolean show_d) {
			this.show_d = show_d;
		}

		public void showDirected(boolean b) {
			show_d = b;
		}


		public boolean evaluate(Context<Graph<V, E>, E> context) {
			Graph<V, E> graph = context.graph;
			E e = context.element;
			if (show_d) {
				return true;
			}
			return false;
		}
	}

	public static void main(String[] args) {
		// TODO Auto-generated method stub
		Runnable worker = new Runnable() {
			public void run() {
				try {
					
//					UIManager.setLookAndFeel(new SubstanceRavenLookAndFeel());
//					JFrame.setDefaultLookAndFeelDecorated(true);
//				    JDialog.setDefaultLookAndFeelDecorated(true);
					
					JFrame f = new MainAppGUI();
					f.setTitle("WBIA2009 AmoWBIA");
					f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
					f.pack();
					f.setVisible(true);
				} catch (Exception e) {
					e.printStackTrace();
				}
			}
		};

		try {
			SwingUtilities.invokeAndWait(worker);
		} catch (InterruptedException e) {
			e.printStackTrace();
		} catch (InvocationTargetException e) {
			e.printStackTrace();
		}
	}

	@Override
	public void actionPerformed(ActionEvent e) {
		AbstractButton source = (AbstractButton) e.getSource();
		if (source == degreeFilter) {
			smallDegreeFilter.filterSmall(source.isSelected());
		} else if (source == showID) {
			if (source.isSelected()) {
				vv1.getRenderContext().setVertexLabelTransformer(
						new ToStringLabeller());
				vv1.getRenderer().getVertexLabelRenderer().setPosition(
						Renderer.VertexLabel.Position.SE);
			}
		}else if(source == sizeByInDegree)
		{
			if(source.isSelected())
				vertexSizeAspect.setScale(true);
			else 
				vertexSizeAspect.setScale(false);
		}else if(source == showArrow)
		{
			if(source.isSelected())
				showArrowPredicate.showDirected(true);
			else 
				showArrowPredicate.showDirected(false);
		}else if(source == degreeDisButton)
		{
			if(source.isSelected())
			{
				degreeDialog.pack();
				degreeDialog.setVisible(true);
			}else {
				degreeDialog.setVisible(false);
			}
		}else if(source == sccView)
		{
			if(source.isSelected())
			{
				sccViewDialog.pack();
				sccViewDialog.setVisible(true);
			}else{
				sccViewDialog.setVisible(false);
			}
		}
		
		vv1.repaint();
		vv2.repaint();
	}

}
